/*
 *MIT License
 *
 *Copyright (c) 2019 chenshuai cs4380@163.com
 *
 *Permission is hereby granted, free of charge, to any person obtaining a copy
 *of this software and associated documentation files (the "Software"), to deal
 *in the Software without restriction, including without limitation the rights
 *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *copies of the Software, and to permit persons to whom the Software is
 *furnished to do so, subject to the following conditions:
 *
 *The above copyright notice and this permission notice shall be included in all
 *copies or substantial portions of the Software.
 *
 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *SOFTWARE.
 */

package com.cs.cslc.common.biz;

import com.cs.cslc.common.constant.CommonConstant;
import com.cs.cslc.common.constant.HttpStatusConstant;
import com.cs.cslc.common.exception.BusinessException;
import com.cs.cslc.common.mapper.ICommonMapper;
import com.cs.cslc.common.msg.TableResultResponse;
import com.cs.cslc.common.pojo.ParamQuery;
import com.cs.cslc.common.util.BeanUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import tk.mybatis.mapper.entity.Example;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * BaseBiz 单表基本增删改查实现.
 *
 * @author cs
 * @version 1.0
 * @date 2018.11.29
 * @description
 */
@Component
public abstract class BaseBiz<M extends ICommonMapper<T>, T> {
    /**
     * 表字段：isDeleted（是否删除）
     */
    private static final String IS_DELETED_FIELD = "isDeleted";
    /**
     * 表字段：id（主键）
     */
    private static final String ID_FIELD = "id";
    /**
     * 排除表操作记录信息字段
     */
    private static final String[] EXCLUDE_PROPERTIES = {"tenantId", "createTime", "createUuserId", "createUserName",
            "updateTime", "updateUserId", "updateUserName", "isDeleted"};

    @Autowired
    protected M baseMapper;

    /**
     * 设置mapper对象
     *
     * @param baseMapper
     */
    public void setMapper(M baseMapper) {
        this.baseMapper = baseMapper;
    }


    /**
     * 通过主键，获取单条数据
     * <p>
     * 根据查询条件中的属性进行查询，只能有一个返回值，有多个结果是抛出异常，查询条件使用等号。<br/>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param query 查询条件
     * @return
     */
    public T selectOne(ParamQuery query) {
        return this.selectOne(query, null, EXCLUDE_PROPERTIES);
    }

    /**
     * 通过查询条件，获取单条数据
     * <p>
     * 根据查询条件中的属性进行查询，只能有一个返回值，有多个结果是抛出异常，查询条件使用等号。<br/>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param query      查询条件
     * @param properties 指定返回表字段，实体字段名数组
     * @return
     */
    public T selectOne(ParamQuery query, String[] properties) {
        return this.selectOne(query, properties, null);
    }

    /**
     * 通过查询条件，获取单条数据
     * <p>
     * 根据查询条件中的属性进行查询，只能有一个返回值，有多个结果是抛出异常，查询条件使用等号。<br/>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param query             查询条件
     * @param properties        指定返回表字段，实体字段名数组
     * @param excludeProperties 排除返回表字段
     * @return
     */
    public T selectOne(ParamQuery query, String[] properties, String[] excludeProperties) {
        Class<T> clazz = this.getClassByEntity();
        Example example = new Example(clazz);
        this.setExampleField(example, clazz, query);
        // 指定返回表字段
        if (null != properties && properties.length > 0) {
            example.selectProperties(properties);
        }
        // 排除指定字段
        if (null != excludeProperties && excludeProperties.length > 0) {
            example.excludeProperties(excludeProperties);
        }
        return baseMapper.selectOneByExample(example);
    }

    /**
     * 给Example查询对象，添加查询条件主键（id），逻辑删除（isDeleted）
     * <p>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param example 查询对象
     * @param clazz   实体对象
     * @param query   查询条件
     * @return
     */
    private void setExampleField(Example example, Class<T> clazz, ParamQuery query) {
        Example.Criteria criterie = example.createCriteria();
        try {
            //判断是否存在删除字段
            clazz.getDeclaredField(ID_FIELD);
            // 是否存在主键
            criterie.andEqualTo(ID_FIELD, query.get(ID_FIELD));
        } catch (NoSuchFieldException e) {
            throw new BusinessException("缺失查询主键！", HttpStatusConstant.FAIL);
        }

        try {
            clazz.getDeclaredField(IS_DELETED_FIELD);
            // 存在删除字段，默认添加未删除的条件
            criterie.andEqualTo(IS_DELETED_FIELD, CommonConstant.IS_UNDELETED);
        } catch (NoSuchFieldException e) {
            // 不存在逻辑删除字段，则只通过主键查询
        }
    }

    /**
     * 获取全部数据
     * <p>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </P>
     *
     * @param query 查询条件,为 null 则查询所有
     * @return
     */
    public List<T> selectListAll(ParamQuery query) {
        return this.selectFieldListAll(query, null, null);
    }

    /**
     * 获取指定字段结果集多条数据
     * <p>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param fields 实体属性名数组，为null则查询所有
     * @return
     */
    public List<T> selectFieldListAll(String[] fields) {
        return this.selectFieldListAll(null, fields, null);
    }

    /**
     * 获取指定字段结果集多条数据
     * <p>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param query  查询条件,为 null 则查询所有
     * @param fields 实体属性名数组，为null则查询所有
     * @return
     */
    public List<T> selectFieldListAll(ParamQuery query, String[] fields) {
        return this.selectFieldListAll(query, fields, null);
    }

    /**
     * 获取指定字段结果集多条数据
     * <p>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param fields        实体属性名数组，为null则查询所有
     * @param orderByClause 排序字段sql语句.案例："create_time DESC , update_time DESC"
     * @return
     */
    public List<T> selectFieldListAll(String[] fields, String orderByClause) {
        return this.selectFieldListAll(null, fields, orderByClause);
    }

    /**
     * 获取指定字段结果集多条数据
     * <p>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param query         查询条件,为 null 则查询所有非逻辑删除的数据
     * @param fields        实体属性名数组，为null则查询所有
     * @param orderByClause 排序字段sql语句.案例："create_time DESC , update_time DESC"
     * @return
     */
    public List<T> selectFieldListAll(ParamQuery query, String[] fields, String orderByClause) {
        Class<T> clazz = this.getClassByEntity();
        Example example = new Example(clazz);
        if (BeanUtil.isNotEmpty(fields)) {
            example.selectProperties(fields);
        }
        this.queryParam(query, example, clazz);
        // 是否添加排序字段
        if (StringUtils.isNotBlank(orderByClause)) {
            example.setOrderByClause(orderByClause);
        }
        return baseMapper.selectByExample(example);
    }

    /**
     * 通过实体条件，统计数量
     * <p>
     * 根据实体中的属性查询总数，查询条件使用等号
     * </p>
     *
     * @param entity 实体
     * @return
     */
    public int selectCount(T entity) {
        return baseMapper.selectCount(entity);
    }

    /**
     * 通过实体，新增单条数据
     * <p>
     * 保存一个实体，null的属性不会保存，会使用数据库默认值
     * </p>
     *
     * @param entity 实体
     * @return
     */
    public void insertSelective(T entity) {
        baseMapper.insertSelective(entity);
    }

    /**
     * 通过主键id，删除单条数据
     * <p>
     * 根据主键字段进行删除，方法参数必须包含完整的主键属性
     * </p>
     *
     * @param id 主键id
     * @return
     */
    public void deleteById(Object id) {
        if (null == id) {
            throw new BusinessException("请选择待删除对象！", HttpStatusConstant.FAIL);
        }
        baseMapper.deleteByPrimaryKey(id);
    }

    /**
     * 通过实体，删除单条数据
     *
     * @param entity 实体
     * @return
     */
    public int delete(T entity) {
        if (null == entity) {
            throw new BusinessException("请选择待删除对象！", HttpStatusConstant.FAIL);
        }
        return baseMapper.delete(entity);
    }

    /**
     * 通过实体，更新单条数据
     * <p>
     * 根据主键更新实体全部字段，null值会被更新
     * </p>
     *
     * @param entity 实体
     * @return
     */
    public void updateById(T entity) {
        baseMapper.updateByPrimaryKey(entity);
    }

    /**
     * 通过实体，更新单条数据
     * <p>
     * 根据主键更新实体部分属性，null值不会更新
     * </p>
     *
     * @param entity 实体
     * @return
     */
    public void updateSelectiveById(T entity) {
        baseMapper.updateByPrimaryKeySelective(entity);
    }

    /**
     * 根据Example条件进行查询
     *
     * @param example
     * @return
     */
    public List<T> selectByExample(Object example) {
        return baseMapper.selectByExample(example);
    }

    /**
     * 通过查询参数，获取翻页数据
     *
     * @param query 查询参数对象
     * @return
     */
    public TableResultResponse<T> selectTableByParamQuery(ParamQuery query) {
        return this.selectTableByParamQuery(query, null);
    }

    /**
     * 通过查询参数，获取翻页数据
     * <p>
     * 默认记录时间倒序
     * </p>
     *
     * @param query  查询参数对象
     * @param fields 实体属性名数组，为null则查询所有
     * @return
     */
    public TableResultResponse<T> selectTableByParamQuery(ParamQuery query, String[] fields) {
        return this.selectTableByParamQuery(query, fields, "create_time desc");
    }

    /**
     * 通过查询参数，获取翻页数据
     *
     * @param query         查询参数对象
     * @param fields        实体属性名数组，为null则查询所有
     * @param orderByClause 排序字段sql语句.案例："create_time DESC , update_time DESC"
     * @return
     */
    public TableResultResponse<T> selectTableByParamQuery(ParamQuery query, String[] fields, String orderByClause) {
        Class<T> clazz = this.getClassByEntity();
        Example example = new Example(clazz);
        if (BeanUtil.isNotEmpty(fields)) {
            example.selectProperties(fields);
        }
        queryParam(query, example, clazz);
        // 是否添加排序字段
        if (StringUtils.isNotBlank(orderByClause)) {
            example.setOrderByClause(orderByClause);
        }
        Page<Object> result = PageHelper.startPage(query.getPage(), query.getLimit());
        List<T> list = this.selectByExample(example);

        if (BeanUtil.isEmpty(list)) {
            return new TableResultResponse<>(0, new ArrayList<>());
        }
        return new TableResultResponse<>(result.getTotal(), list);
    }

    /**
     * 通过ParamQuery，获取sql查询条件
     * <p>
     * 查询条件都是模糊查询
     * </p>
     *
     * @param query   查询条件,查询条件都是模糊查询
     * @param example 执行sql的对象
     * @param clazz   实体类class
     */
    public void queryParam(ParamQuery query, Example example, Class<T> clazz) {
        // 设置删除参数
        try {
            // 判断是否存在删除字段
            Field field = clazz.getDeclaredField(IS_DELETED_FIELD);
            // 存在删除字段，默认添加未删除的条件
            if (field != null) {
                if (BeanUtil.isEmpty(query)) {
                    query = new ParamQuery();
                }
                query.put(IS_DELETED_FIELD, CommonConstant.IS_UNDELETED);
            }
        } catch (NoSuchFieldException e) {
            // 没有逻辑删除字段，则不做逻辑删除处理
        }
        // 设置查询参数
        if (BeanUtil.isNotEmpty(query)) {
            Example.Criteria criteria = example.createCriteria();
            Object value;
            for (Map.Entry<String, Object> entry : query.entrySet()) {
                value = entry.getValue();
                // 字符串则做模糊匹配
                if (value instanceof String) {
                    criteria.andLike(entry.getKey(), "%" + entry.getValue().toString() + "%");
                } else {
                    criteria.andEqualTo(entry.getKey(), entry.getValue().toString());
                }
            }
        }

    }

    /**
     * 获取当前实体的class对象
     *
     * @return
     */
    private Class getClassByEntity() {
        Class<T> clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
        return clazz;
    }
}